#include <ne.h>
#include <system/GlutApplication.h>

ne::Vector2 mousePosition;
bool mouseLeftOn = false;
bool mouseMiddleOn = false;
bool mouseRightOn = false;
ne::Engine *pEngine = 0;
ne::Timer timer;
ne::Camera *camera = 0;
ne::Viewport *viewport = 0;
ne::GlutApplication *pApplication = 0;
ne::TexturePtr pTexture;

const ne::real COS_1_36_PI = 0.996194698f;
const ne::real TAN_1_8_PI = 0.414213562373f;
const ne::real INVERTED_SQRT2 = 0.707106781186547524401f;

float DirectionWeight[4];
float SumWeight = 1.0f;

std::list<ne::Vector2> originalPoints;
std::list<ne::Vector2> points;
std::list<ne::Vector2> simplePoints;

void keyboard(unsigned char key, int x, int y);
void keyboardUp(unsigned char key, int x, int y);
void mouseFunc(int button, int state, int x, int y);
void motionFunc(int x, int y);
void loop(void);
void redraw(void);
void drawFPS(void);
void resize(int width, int height);
void init();

void calWeight(std::list<ne::Vector2> &pointList)
{
    SumWeight = 0.000001f;
    DirectionWeight[0] = 0.0f;
    DirectionWeight[1] = 0.0f;
    DirectionWeight[2] = 0.0f;
    DirectionWeight[3] = 0.0f;

    std::list<ne::Vector2>::iterator it = pointList.begin();
    while (it != pointList.end())
    {
        ne::Vector2 p0 = *it;
        ++it;
        if (it != pointList.end())
        {
            ne::Vector2 p1 = *it;

            ne::Vector2 line = p1 - p0;
            float d = line.length();
            ne::Vector2 v = line.getUnit();
            float absX = std::abs(v.x);
            float absY = std::abs(v.y);

            if (TAN_1_8_PI * absX > absY)
            {
                DirectionWeight[0] += d;
            }
            else if (TAN_1_8_PI * absY > absX)
            {
                DirectionWeight[1] += d;
            }
            else if (line.x * line.y > 0.0f)
            {
                DirectionWeight[2] += d;
            }
            else
            {
                DirectionWeight[3] += d;
            }
            SumWeight += d;
        }
        else
        {
            break;
        }
    }
}

ne::Vector2 format(const ne::Vector2 &p0, const ne::Vector2 &p1)
{
    ne::Vector2 line = p1 - p0;
    ne::Vector2 v = line.getUnit();
    float absX = std::abs(v.x);
    float absY = std::abs(v.y);

    ne::Vector2 p = p1;

    if (TAN_1_8_PI * absX > absY)
    {
        p.y = p0.y;
    }
    else if (TAN_1_8_PI * absY > absX)
    {
        p.x = p0.x;
    }
    else if (line.x * line.y > 0.0f)
    {
        float d = line.dotProduct(ne::Vector2(0.5f, 0.5f));
        p.x = p0.x + d;
        p.y = p0.y + d;
    }
    else
    {
        float d = line.dotProduct(ne::Vector2(0.5f, -0.5f));
        p.x = p0.x + d;
        p.y = p0.y - d;
    }

    return p;
}

void eraseUselessPointBack(std::list<ne::Vector2> &pointList)
{
    if (pointList.size() > 0)
    {
        ne::Vector2 p2 = pointList.back();
        pointList.pop_back();
        while (pointList.size() >= 2)
        {
            ne::Vector2 p1 = pointList.back();
            pointList.pop_back();
            ne::Vector2 p0 = pointList.back();

            ne::Vector2 v01 = p1 - p0;
            ne::Vector2 v12 = p2 - p1;
            if (v12.squaredLength() >= 2)
            {
                ne::Vector2 d01 = v01.getUnit();
                ne::Vector2 d12 = v12.getUnit();
                if (d01.dotProduct(d12) < COS_1_36_PI)
                {
                    pointList.push_back(p1);
                    break;
                }
            }
        }
        pointList.push_back(p2);
    }
}

void addPoint(float x, float y)
{
    ne::Vector2 p1(x, y);
    originalPoints.push_back(p1);
    if (points.size() > 1)
    {
        ne::Vector2 p0 = points.back();
        ne::Vector2 pm = (p1 + p0) * 0.5f;

        ne::Vector2 a0 = format(p0, pm);
        ne::Vector2 a1 = format(p1, pm);
        ne::Vector2 v = (a0 - p0).getUnit() * (a0 - pm).length();
        a0 -= v;
        a1 += v;

        points.push_back(a0);
        eraseUselessPointBack(points);

        points.push_back(a1);
        eraseUselessPointBack(points);
    }
    points.push_back(p1);
}

void cullPoints(std::list<ne::Vector2> &pointList)
{
    std::list<ne::Vector2>::iterator it = pointList.begin();
    while (it != pointList.end())
    {
        ne::Vector2 p0 = *it;
        std::list<ne::Vector2>::iterator inext = it;
        ++inext;
        if (inext != pointList.end())
        {
            ne::Vector2 p1 = *inext;
            it = inext;
            ++it;
            while (it != pointList.end())
            {
                ne::Vector2 p2 = *it;
                ne::Vector2 v01 = (p1 - p0).getUnit();
                ne::Vector2 v12 = (p2 - p1).getUnit();
                if (v01.dotProduct(v12) > COS_1_36_PI)
                {
                    pointList.erase(inext);
                    inext = it;
                    p1 = p2;
                    ++it;
                }
                else
                {
                    it = inext;
                    break;
                }
            }
        }
        else
        {
            break;
        }
    }
}

int main(int argc, char *argv[]) /* I - Command-line arguments */
{
    pApplication = new ne::GlutApplication();//(argc, argv);
    glutReshapeFunc(resize);
    glutDisplayFunc(loop);
    glutKeyboardFunc(keyboard);
    glutMouseFunc(mouseFunc);
    glutMotionFunc(motionFunc);

    pEngine = pApplication->engine;
    if (!pEngine || !pEngine->isReady())
    {
        delete pApplication;
        return 0;
    }

    init();

    glutMainLoop();

    delete pApplication;

    return 0;
}

void init()
{
    camera = new ne::Camera();
    camera->setProjectionType(ne::PT_ORTHOGRAPHIC);
    camera->setPosition(ne::Vector3::UNIT_Z * 10);
    viewport = new ne::Viewport(camera);

    glClearColor(0.1f, 0.1f, 0.2f, 0.0f);
    glClearDepth(1);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glDepthFunc(GL_LEQUAL);
    glDisable(GL_DEPTH_TEST);

    glCullFace(GL_BACK);
    glDisable(GL_CULL_FACE);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    glDisable(GL_LIGHTING);

    glShadeModel(GL_SMOOTH);
    glEnable(GL_POINT_SMOOTH);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_LINE_SMOOTH);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_POLYGON_SMOOTH);
    glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
}

void keyboard(unsigned char key, int x, int y)
{
    /*
    if (key == 'w' || key == 'W')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 's' || key == 'S')
    {
        camera->translate(ne::Vector3::UNIT_Z, ne::TS_LOCAL);
    }
    else if (key == 'a' || key == 'A')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'd' || key == 'D')
    {
        camera->translate(ne::Vector3::UNIT_X, ne::TS_LOCAL);
    }
    else if (key == 'o' || key == 'O')
    {
        camera->translate(ne::Vector3::NEGATIVE_UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'u' || key == 'U')
    {
        camera->translate(ne::Vector3::UNIT_Y, ne::TS_PARENT);
    }
    else if (key == 'q' || key == 'Q')
    {
        camera->yaw(ne::Math::PI / 36);
    }
    else if (key == 'e' || key == 'E')
    {
        camera->yaw(- ne::Math::PI / 36);
    }
    else if (key == 'i' || key == 'I')
    {
        camera->pitch(ne::Math::PI / 36);
    }
    else if (key == 'k' || key == 'K')
    {
        camera->pitch(- ne::Math::PI / 36);
    }
    else if (key == 'j' || key == 'J')
    {
        camera->roll(ne::Math::PI / 36);
    }
    else if (key == 'l' || key == 'L')
    {
        camera->roll(- ne::Math::PI / 36);
    }
    else if (key == 'n' || key == 'N')
    {
        lineMode ^= 1;
    }
    else if (key == 'p' || key == 'P')
    {
        pModel->playAnimation("TestAnimation", 0, false);
    }
    else if (key == 'z' || key == 'Z')
    {
        light->setPosition(0.5f * light->getPosition());
    }
    else if (key == 'c' || key == 'C')
    {
        light->setPosition(2.0f * light->getPosition());
    }
    else */if (key == 0x1b)
    {
        exit(1);
    }
}

void keyboardUp(unsigned char key, int x, int y)
{
}

void mouseFunc(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseLeftOn = true;
        originalPoints.clear();
        points.clear();
        simplePoints.clear();
    }
    else if (button == GLUT_LEFT_BUTTON && state == GLUT_UP)
    {
        mouseLeftOn = false;
        simplePoints = originalPoints;
        cullPoints(simplePoints);
        calWeight(points);
    }

    if (button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseMiddleOn = true;
    }
    else if (button == GLUT_MIDDLE_BUTTON && state == GLUT_UP)
    {
        mouseMiddleOn = false;
    }

    if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
    {
        mousePosition.x = x;
        mousePosition.y = y;
        mouseRightOn = true;
    }
    else if (button == GLUT_RIGHT_BUTTON && state == GLUT_UP)
    {
        mouseRightOn = false;
    }
}

void motionFunc(int x, int y)
{
    if (mouseLeftOn)
    {
        addPoint(x, y);
    }

    /*
    if (mouseMiddleOn)
    {
        float dx = x - mousePosition.x;
        ParallaxFactor = ne::Math::Clamp(ParallaxFactor + 0.0002 * dx, 0.0, 0.1);
    }

    if (mouseRightOn)
    {
        float dx = x - mousePosition.x;
        float dy = y - mousePosition.y;
        ne::Quaternion q(ne::Vector3::UNIT_Y, -0.01f * dx);
        ne::Vector3 vOffset = camera->getPosition();
        camera->setPosition(q * vOffset);
        camera->rotate(q, ne::TS_WORLD);

        q.fromAxisAngle(camera->getLeft(), 0.01f * dy);
        vOffset = camera->getPosition();
        if ((vOffset.crossProduct(ne::Vector3::UNIT_Y)).dotProduct((q * vOffset).crossProduct(ne::Vector3::UNIT_Y)) > 0.0f)
        {
            camera->setPosition(q * vOffset);
            camera->rotate(q, ne::TS_WORLD);
        }
    }
    */

    mousePosition.x = x;
    mousePosition.y = y;
}

void loop(void)
{
    redraw();
}

void redraw(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    const ne::Matrix4 &viewMatrix = camera->getViewMatrix();
    pEngine->getRenderSystem()->setViewMatrix(viewMatrix);

    glPushMatrix();
        glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
        glBegin(GL_QUADS);
            //glTexCoord2f(0.0f, 1.0f);
            glVertex3f(-10.0f, 10.0f, 0.0f);
            //glTexCoord2f(0.0f, 0.0f);
            glVertex3f(-10.0f, -10.0f, 0.0f);
            //glTexCoord2f(1.0f, 0.0f);
            glVertex3f(10.0f, -10.0f, 0.0f);
            //glTexCoord2f(1.0f, 1.0f);
            glVertex3f(10.0f, 10.0f, 0.0f);
        glEnd();

        glBegin(GL_LINES);
            glVertex3f(-1000.0f, 0.0f, 0.0f);
            glVertex3f(1000.0f, 0.0f, 0.0f);

            glVertex3f(0.0f, -1000.0f, 0.0f);
            glVertex3f(0.0f, 1000.0f, 0.0f);

            glVertex3f(-1000.0f, 1000.0f, 0.0f);
            glVertex3f(1000.0f, -1000.0f, 0.0f);

            glVertex3f(-1000.0f, -1000.0f, 0.0f);
            glVertex3f(1000.0f, 1000.0f, 0.0f);
        glEnd();
    glPopMatrix();

    drawFPS();

    glutSwapBuffers();
    glutPostRedisplay();
}

void resize(int width, int height)
{
    /* Reset the viewport->.. */
    glViewport(0, 0, width, height);
    viewport->setPosition(0, 0);
    viewport->resize(width, height);

    const ne::Matrix4 &projectionMatrix = camera->getProjectionMatrix();
    pEngine->getRenderSystem()->setProjectionMatrix(projectionMatrix);
}

void drawFPS()
{
    glClear(GL_DEPTH_BUFFER_BIT);

    //Set matrices for ortho
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0.0f, viewport->getWidth(),
            0.0f, viewport->getHeight(),
            1.0f, -1.0f);

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

    //Print text
    ne::FontPtr font = pEngine->getFont();
    ne::Vector3 pos(viewport->getWidth() - 200.0f, 10.0f, 0.0f);
    float w = font->getFontWidth();
    float h = font->getFontHeight();

    char debugString[32];
    ne::real interval = timer.getIntervalMilliseconds();
	sprintf(debugString, "FPS : %.2f%%", 1000.0f / interval);
    font->renderText2D(debugString, pos, 32.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));
    pos.y += 16.0f;

	sprintf(debugString, "Weight - : %.2f%%", 100.0f * DirectionWeight[0] / SumWeight);
    font->renderText2D(debugString, pos, 32.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));
    pos.y += 16.0f;

	sprintf(debugString, "Weight | : %.2f%%", 100.0f * DirectionWeight[1] / SumWeight);
    font->renderText2D(debugString, pos, 32.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));
    pos.y += 16.0f;

	sprintf(debugString, "Weight \\ : %.2f%%", 100.0f * DirectionWeight[2] / SumWeight);
    font->renderText2D(debugString, pos, 32.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));
    pos.y += 16.0f;

	sprintf(debugString, "Weight / : %.2f%%", 100.0f * DirectionWeight[3] / SumWeight);
    font->renderText2D(debugString, pos, 32.0f*w, h, ne::Color(0.8f, 1.0f, 0.0f, 1.0f));


    std::list<ne::Vector2>::iterator it, iend;
    glPushMatrix();
        // originalPoints
        iend = originalPoints.end();
        glLineWidth(4.0f);
        glColor4f(0.0f, 0.5f, 1.0f, 1.0f);
        glBegin(GL_LINE_STRIP);
            for (it=originalPoints.begin(); it!=iend; ++it)
            {
                glVertex2f(it->x, it->y);
            }
        glEnd();


        // simplePoints
        iend = simplePoints.end();
        glLineWidth(2.0f);
        glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
        glBegin(GL_LINE_STRIP);
            for (it=simplePoints.begin(); it!=iend; ++it)
            {
                glVertex2f(it->x, it->y);
            }
        glEnd();
        glLineWidth(1.0f);

        glPointSize(8.0f);
        glBegin(GL_POINTS);
            for (it=simplePoints.begin(); it!=iend; ++it)
            {
                glVertex2f(it->x, it->y);
            }
        glEnd();

        // points
        iend = points.end();
        glColor4f(0.2f, 1.0f, 0.0f, 1.0f);
        glBegin(GL_LINE_STRIP);
            for (it=points.begin(); it!=iend; ++it)
            {
                glVertex2f(it->x, it->y);
            }
        glEnd();

        glPointSize(4.0f);
        uint32_t i = 0;
        uint32_t isize = points.size();
        glColor4f(0.8f, 1.0f, 0.125f, 0.8f);
        glBegin(GL_POINTS);
            for (it=points.begin(); (it!=iend && i+1<isize); ++i, ++it)
            {
                glVertex2f(it->x, it->y);
            }
        glEnd();

        if (isize > 0)
        {
            glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
            glBegin(GL_POINTS);
                ne::Vector2 p = points.back();
                glVertex2f(p.x, p.y);
            glEnd();
        }
        glPointSize(1.0f);
    glPopMatrix();


    //reset matrices
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}
